import java.util.LinkedList;

/**
 * 
 * Determines next process to run dependent on user provided algorithm.
 * @author Patrick Black
 *
 */
public class Scheduler {
	// queues for various algorithms and states.
	private schedQueue ready = new schedQueue();
	private schedQueue blocked = new schedQueue();
	
	// queues for priorties
	private schedQueue high = new schedQueue();
	private schedQueue medium = new schedQueue();
	private schedQueue low = new schedQueue();
	
	//for printing
	private schedQueue throwAway = new schedQueue();
	
	//determines which algorithm to use.
	protected int algorithm;
	
	/**
	 * creates a scheduler and adds processes.
	 */
	public Scheduler() {
		Process p1 = new Process("Calc", 4000, 10, "Aux");
		Process p2 = new Process("UI", 4000, 10, "IO");
		Process p3 = new Process("Consumer", 4000, 5, "Read");
		Process p4 = new Process("Producer", 4000, 15, "Write");
		
		addProcess(p2);
		addProcess(p1);
		addProcess(p3);
		addProcess(p4);
	}
	
	/**
	 * 
	 * @param choice algorithm to use.
	 */
	protected void setAlgorithm(int choice) {
		algorithm = choice;
	}
	
	/**
	 * 
	 * @param process process to add to the queue.
	 */
	protected void addProcess(Process process) {
		/*
		System.out.println("h before add: " + high);
		System.out.println("m before add: " + medium);
		System.out.println("l before add: " + low);
		*/
		
		//System.out.println("blocked before add: " + blocked);
		if (process.fetchState()==(Process.procState.READY)) {
			//System.out.println(process.fetchName() + " to ready");
			ready.enqueue(process);
			if (process.getPriority() == 15) {
				high.enqueue(process);
			} else if (process.getPriority() == 10) {
				medium.enqueue(process);
			} else if (process.getPriority() == 5) {
				low.enqueue(process);
			}
		} else {
			//System.out.println(process.fetchName() + " to blocked");
			blocked.enqueue(process);
		} 
		/*
		System.out.println("h after add: " + high);
		System.out.println("m after add: " + medium);
		System.out.println("l after add: " + low);
		System.out.println("blocked after add: " + blocked);
		*/
	}
	
	/**
	 * changes state of blocked process to ready. moves to ready queue.
	 * @param process process to be unblocked.
	 */
	protected void unBlock(Process process) {
		
		Process next = blocked.dequeue();
		int size = 0;
		while(!next.fetchName().equals(process.fetchName()) && size != blocked.size()) {
			blocked.enqueue(next);
			next = blocked.dequeue();
			size++;
		}
		
		if(next.fetchName().equals(process.fetchName())) {
			next.setState(Process.procState.READY);
			ready.enqueue(next);
			if (next.getPriority() == 15) {
				high.enqueue(next);
			} else if (next.getPriority() == 10) {
				medium.enqueue(next);
			} else if (next.getPriority() == 5) {
				low.enqueue(next);
			}
		} else {
			blocked.enqueue(next);
		}
	}
	
	/**
	 * unblocks a consumer or producer dependent on which memory location they are waiting on.
	 * @param waitingOn which memory location process is blocked for.
	 */
	protected void unBlockConsumerProducer(int waitingOn) {
		Process next = blocked.dequeue();
		int size = 0;
		while(next.getWaitingOn() != (waitingOn) && size != blocked.size()) {
			blocked.enqueue(next);
			next = blocked.dequeue();
			size++;
		}
		if(next.getWaitingOn() == (waitingOn)) {
			next.setState(Process.procState.READY);
			ready.enqueue(next);
		if (next.getPriority() == 15) {
				high.enqueue(next);
			} else if (next.getPriority() == 10) {
				medium.enqueue(next);
			} else if (next.getPriority() == 5) {
				low.enqueue(next);
			}
		} else {
			blocked.enqueue(next);
		}
	}
	
	/**
	 * 
	 * @return next process to run.
	 */
	protected Process nextProcess() {
		Process next = null;
		printStatus();
		/*
		System.out.println("h after send: " + high);
		System.out.println("m after send: " + medium);
		System.out.println("l after send: " + low);
		System.out.println("blocked send add: " + blocked);*/
		switch(algorithm) {
		case 1:
			next = nextPri();
			break;
		case 2:
			next = nextLottery();
			break;
		case 0:
			next = nextRR();
			break;
		}
		System.out.println("Scheduler: Process " + next.fetchName() + " running.");
		System.out.println();
		next.changePriority();
		return next;
	}
	
	/**
	 * road robin algorithm
	 * @return next process in the queue (since all processes get the same amount of time)
	 */
	private Process nextRR() {
		Process send = null;
		if(ready.hasProcesses())
			send = ready.dequeue();
		return send;
	}
	
	/**
	 * priority algorithm
	 * @return next highest level priority process
	 */
	private Process nextPri() {
		Process send = null;
		if (high.hasProcesses()) {
			send = high.dequeue();
		} else if (medium.hasProcesses()) {
			send = medium.dequeue();
		} else {
			send = low.dequeue();
		}
		
		return send;
	}
	
	/**
	 * lottery algorithm... doesnt work yet.
	 * @return next process by lottery selection
	 */
	private Process nextLottery() {
		Process send = null;
		int min = 0;
		int max = 10;
		int rand = min + (int)(Math.random()*max);
		
		send = ready.dequeue();
		for (int i = 0; i <= rand; i++) {
			ready.enqueue(send);
			send = ready.dequeue();
		}
		
		return send;
	}
	
	/**
	 * prints the state of all processes.
	 */
	private void printStatus() {
		
		if(algorithm == 0 || algorithm == 2) {
			while(ready.hasProcesses()) {
				throwAway.enqueue(ready.dequeue());
			}
			while(throwAway.hasProcesses()) {
				Process p = throwAway.dequeue();
				System.out.println("Process " + p.fetchName() + " state: " + p.fetchState());
				ready.enqueue(p);
			}
			while(blocked.hasProcesses()) {
				throwAway.enqueue(blocked.dequeue());
			}
			while(throwAway.hasProcesses()) {
				Process p = throwAway.dequeue();
				System.out.println("Process " + p.fetchName() + " state: " + p.fetchState());
				blocked.enqueue(p);
			}
		} else if (algorithm == 1) {
			while(high.hasProcesses()) {
				throwAway.enqueue(high.dequeue());
			}
			while(throwAway.hasProcesses()) {
				Process p = throwAway.dequeue();
				System.out.println("Process " + p.fetchName() + " state: " + p.fetchState());
				high.enqueue(p);
			}
			while(medium.hasProcesses()) {
				throwAway.enqueue(medium.dequeue());
			}
			while(throwAway.hasProcesses()) {
				Process p = throwAway.dequeue();
				System.out.println("Process " + p.fetchName() + " state: " + p.fetchState());
				medium.enqueue(p);
			}
			while(low.hasProcesses()) {
				throwAway.enqueue(low.dequeue());
			}
			while(throwAway.hasProcesses()) {
				Process p = throwAway.dequeue();
				System.out.println("Process " + p.fetchName() + " state: " + p.fetchState());
				low.enqueue(p);
			}
			while(blocked.hasProcesses()) {
				throwAway.enqueue(blocked.dequeue());
			}
			while(throwAway.hasProcesses()) {
				Process p = throwAway.dequeue();
				System.out.println("Process " + p.fetchName() + " state: " + p.fetchState());
				blocked.enqueue(p);
			}
		}
	}
	/**
	 * 
	 * @return true if blocked queue is empty.
	 */
	public boolean emptyBlocked() {
		boolean empty = false;
		if(blocked.size() == 0) {
			empty = true;
		}
		return empty;
	}
	
	
	/**
	 * Queue of processes.
	 * 
	 * @author Patrick Black
	 */
 	private class schedQueue extends LinkedList {
 		int size = 0;
		public void enqueue(Process process) {
			addLast(process);
			size++;
		}
	
		public Process dequeue() {
			size--;
			return (Process) poll();
		}
		
		public boolean hasProcesses() {
			return !isEmpty();
		}
		
		public int size() {
			return size;
		}
	}
}
